<?php
/**
 * Created by User: wene<china_wangyu@aliyun.com> Date: 2019/4/11 Time: 17:38
 */

namespace think\restful\reflex;

use think\restful\Base;
use think\restful\exception\ApiException;

/**
 * Class Reflex Api反射类
 * @package think\restful\reflex
 */
class Reflex extends Base
{
    /**
     * @var \ReflectionClass $reflectionObject
     */
    public $reflectionObject;
    /**
     * @var \think\restful\Api $apiObject
     */
    public $apiObject;
    /**
     * @var array $trims 注释提炼规则
     */
    private $trims =  array('     ', '/**', '*/', "\t", "\n", "\r", '$', '*');
    /**
     * @var array $apiReflexDefault api方法反射路由
     */
    private $apiReflexParamDefault = [
        'doc' => ['declare','value'], // 定义：方法名称
        'route' => ['declare','route','method'], // 定义：路由
        'param' => ['declare','type','name','doc','rule','default'], // 定义：参数
        'success' => ['declare','value'], // 定义：返回成功名称
        'error' => ['declare','value'], // 定义：返回失败名称
    ];

    public function __construct(\think\restful\Api $api)
    {
        parent::__construct();
        if($this->reflectionObject instanceof \think\restful\Api){
            ApiException::exception('本PHP扩展反射类，只接受 think\restful\Api 子类');
        }
        $this->reflectionObject = new \ReflectionClass($api);
        $this->apiObject = $api;
    }

    /**
     * 获取对象反射数据，设置$action就取方法的反射数据
     * @param string $action
     * @return array
     */
    public function get(string $action = ''):array
    {
        if(empty($this->reflectionObject)){
            ApiException::exception('请先实例化 \think\restful\reflex\Api类');
        }
        if (empty($action)){
            return $this->object();
        }else{
            return $this->action($action);
        }
    }

    /**
     * 获取对象反射文档
     * @return array|null
     */
    public function object():?array
    {
        try{
            $objReflexString = $this->reflectionObject->getDocComment();
            $objReflexArray = $this->clean($objReflexString);
            $actions = get_class_methods($this->apiObject);
            $actionReflexArrays = [];
            !empty($this->config['API_IGNORE_METHOD']) && $actions = array_diff($actions,$this->config['API_IGNORE_METHOD']);
            foreach ($actions as $index => $action){
                if (stristr($action,'__') !== false){
                    unset($actions[$index]);
                }else{
                    $actionReflexArrays[$action] = $this->action($action);
                }
            }
            $returnData = ['object'=>$objReflexArray,'methods'=>$actionReflexArrays];
            return $returnData;
        }catch (\Exception $exception){
            ApiException::exception('获取对象反射参数失败~'.$exception->getMessage());
        }
    }

    /**
     * 获取方法反射文档
     * @param string $action
     * @return array|null
     */
    public function action(string $action):?array
    {
        try{
            $actionReflexObject = $this->reflectionObject->getMethod($action);
            $actionReflexDoc = trim($actionReflexObject->getDocComment());
            $actionReflexArray = $this->clean($actionReflexDoc);
            unset($actionReflexArray[0]);
            $actionReflexArray = array_values($actionReflexArray);
            $actionReflexArray = $this->group($actionReflexArray,' ');
            $actionReflexArray = $this->filter($actionReflexArray);
            return $actionReflexArray;
        }catch (\Exception $exception){
            ApiException::exception('方法反射文档编写有问题，~'.$exception->getMessage());
        }
    }

    /**
     * 清洗数据
     * @param string $reflexString 类方法注释
     * @return array
     */
    public function clean(string $reflexString): array
    {
        $newReflexString = str_replace($this->trims, '', trim($reflexString));
        $reflexArray = explode('@', $newReflexString);
        return $reflexArray;
    }

    /**
     * 过滤器(组)
     * @param array $reflex 类方法注释
     * @param string $rule 规则
     * @return array
     */
    public static function group(array $reflex,string $rule):array
    {
        try{
            $argc = [];
            array_walk($reflex, function ($item) use (&$argc,$rule) {
                $param = explode($rule,trim($item));
                if (!isset($argc[$param[0]])){
                    $argc[$param[0]] = $param;
                }elseif (isset($argc[$param[0]][0]) and is_string($argc[$param[0]][0])){
                    $argc[$param[0]] = [$argc[$param[0]],$param];
                }elseif (isset($argc[$param[0]][0]) and is_array($argc[$param[0]][0])){
                    array_push($argc[$param[0]],$param);
                }
            });
            return $argc;
        }catch (\Exception $exception){
            ApiException::exception('方法反射文档编写有问题，'.$exception->getMessage());
        }
    }

    /**
     * API文档参数过滤
     * @param array $reflex 反射文档
     * @return null
     */
    public function filter(array $reflex){
        try{
            // 定义参数文档
            $paramDefines = $this->config['API_ACTION_DOCUMENT_DEFINE'];
            empty($paramDefines) && $paramDefines = $this->apiReflexParamDefault;
            if(array_keys($paramDefines) !== array_keys($reflex)){
                ApiException::exception('方法反射文档编写有问题，参数name不一致~');
            }
            $reflexArray = [];
            $reflexParamArray = [];
            foreach ($reflex as $doc_name => $doc_params){
                if (is_array($doc_params[0])){
                    foreach ($doc_params as $key => $param){
                        $len = count($paramDefines[$doc_name]) - count($param);
                        if ($len > 0){
                            foreach (range(1,$len) as $key){
                                array_push($param,'');
                            }
                        }
                        $reflexParamArray[] = array_combine($paramDefines[$doc_name],$param);
                    }
                    $reflexArray[$doc_name] = $reflexParamArray;
                }else{
                    $reflexArray[$doc_name] = array_combine($paramDefines[$doc_name],$doc_params);
                }
            }
            return $reflexArray;
        }catch (\Exception $exception){
            ApiException::exception('方法反射文档编写有问题~'.$exception->getMessage());
        }
    }

}